//------------------------------------------------------------
//  Filename: vga_dma_master.v
//   
//  Author  : wlduan@gmail.com
//  Revise  : 2016-10-07 17:12
//  Description: 
//   
//  Copyright (C) 2014, YRBD, Inc. 					      
//  All Rights Reserved.                                       
//-------------------------------------------------------------
//
`timescale 1ns/1ps
 
module VGA_DMA_MASTER #( 
    parameter integer C_M_AXI_BURST_LEN      = 16,
    parameter integer C_M_AXI_ID_WIDTH       = 1,
    parameter integer C_M_AXI_ADDR_WIDTH     = 32,
    parameter integer C_M_AXI_DATA_WIDTH     = 32,
    parameter integer C_M_AXI_AWUSER_WIDTH   = 0,
    parameter integer C_M_AXI_WUSER_WIDTH    = 0,
    parameter integer C_M_AXI_BUSER_WIDTH    = 0
)( 
   
    //************* Read channel ***********
    input  wire                               M_AXI_ACLK,
    input  wire                               M_AXI_ARESETN,
    output reg  [C_M_AXI_ADDR_WIDTH-1 : 0   ] M_AXI_ARADDR,
    output wire [7 : 0                      ] M_AXI_ARLEN,
    output wire [2 : 0                      ] M_AXI_ARSIZE,
    output wire [1 : 0                      ] M_AXI_ARBURST,
    output wire [3 : 0                      ] M_AXI_ARCACHE,
    output reg                                M_AXI_ARVALID,
    input  wire                               M_AXI_ARREADY,
    input  wire [C_M_AXI_DATA_WIDTH-1 : 0   ] M_AXI_RDATA,
    input  wire [1 : 0                      ] M_AXI_RRESP,
    input  wire                               M_AXI_RLAST,
    input  wire                               M_AXI_RVALID,
    output wire                               M_AXI_RREADY,
    //************** fifo interface **********************
    input  wire                               disp_rd_clk,
    input  wire                               disp_rd_en,
    output wire                               disp_empty,
    output wire [32:0                       ] disp_dout,
    input  wire                               vga_v_sync,
    //************** config regs *************************
    input  wire                               video0_only,
    input  wire                               video1_only,
    input  wire                               menu_only,   
    input  wire [31:0                       ] video0_base_addr,
    input  wire [31:0                       ] video1_base_addr,
    output reg  [31:0                       ] videos_disp_cnt,
    input  wire [31:0                       ] menu_base_addr,
    output reg  [1:0                        ] disp_flow_err,   
    output reg  [1:0                        ] menu_flow_err,   
    input  wire [31:0                       ] disp_pix_cnt,
    input  wire                               mm_disp_en,   
    input  wire                               mm_menu_en   
);  
//--------------------------------------------------------
wire clk = M_AXI_ACLK;
wire frame_last;
wire prog_full;
wire prog_empty;
wire mm_video_en = ~menu_only;
//--------------------------------------------------------
reg[1:0]  v_sync_ffx;
always @(posedge clk) v_sync_ffx <= {v_sync_ffx[0],vga_v_sync};
//--------------------------------------------------------
reg  resetn;
//--------------------------------------------------------
always @(posedge clk ,negedge M_AXI_ARESETN) begin
    if(~M_AXI_ARESETN) begin 
        resetn <= 'b0;
    end
    else begin
        resetn <= (v_sync_ffx == 2'b10)?1'b0:1'b1; //neg edge
    end 
end
//--------------------------------------------------------
parameter   IDLE        = 8'b0000_0001;
parameter   DISP_ADDR   = 8'b0000_0010;  
parameter   DISP_DATA   = 8'b0000_0100;  
parameter   WAIT_FIFO   = 8'b0000_1000;  
parameter   NEXT_READ   = 8'b0001_0000;  
parameter   NEXT_FRAME  = 8'b0010_0000; 
parameter   MENU_ADDR   = 8'b0100_0000; 
parameter   MENU_DATA   = 8'b1000_0000; 
//--------------------------------------------------------
reg [7:0]   cur_state;  
reg [7:0]   nxt_state;  
reg [1:0]   data_phase;
//--------------------------------------------------------
always @(posedge clk,negedge resetn) begin
    if(~resetn)begin 
        cur_state <= IDLE;  
    end  
    else  begin  
        cur_state <= nxt_state;  
    end  
end  
//--------------------------------------------------------
always @(*) begin  
    nxt_state  = cur_state;  
    case(cur_state)  
        IDLE: begin  
            if(mm_disp_en) begin 
                nxt_state  = mm_video_en ? DISP_ADDR : MENU_ADDR ; 
            end 
        end  
        DISP_ADDR: begin
            if(M_AXI_ARREADY & M_AXI_ARVALID) begin  
                nxt_state  = DISP_DATA; 
            end 
        end    
        DISP_DATA: begin
            if(M_AXI_RREADY & M_AXI_RVALID & M_AXI_RLAST) begin  
                if(mm_menu_en)
                    nxt_state  = MENU_ADDR;  
                else
                    nxt_state  = WAIT_FIFO ;  
            end
        end
        MENU_ADDR: begin  
            if(M_AXI_ARREADY & M_AXI_ARVALID) begin 
                nxt_state  = MENU_DATA;  
            end
        end  
        MENU_DATA: begin  
            if(M_AXI_RREADY & M_AXI_RVALID & M_AXI_RLAST) begin    
                nxt_state  = WAIT_FIFO;  
            end
        end  
        WAIT_FIFO: begin  
            if(frame_last)  begin  
                nxt_state  = NEXT_FRAME;  
            end
            else if((mm_menu_en&(~menu_full))|((~mm_menu_en)&(~prog_full))) begin
                nxt_state  = NEXT_READ; 
            end 
        end  
        NEXT_READ: begin  
            nxt_state  = (data_phase == 2'b0) ? DISP_ADDR : MENU_ADDR;
        end  
        NEXT_FRAME: begin  
            nxt_state  = cur_state;
        end  
    endcase  
end  
//--------------------------------------------------------
reg[31:0] camx_frm_addr;
//--------------------------------------------------------
always @(posedge clk,negedge resetn) begin
    if(~resetn)begin 
        camx_frm_addr <= 32'b0;    
    end 
    else if((cur_state == DISP_DATA)&&(M_AXI_RVALID)&&(M_AXI_RREADY)) begin 
        camx_frm_addr <= camx_frm_addr + 32'b1;    
    end
end 
//--------------------------------------------------------
reg[31:0] menu_frm_addr;
//--------------------------------------------------------
always @(posedge clk,negedge resetn) begin
    if(~resetn)begin 
        menu_frm_addr <= 32'b0;    
    end 
    else if((cur_state == MENU_DATA)&&(M_AXI_RVALID)&&(M_AXI_RREADY)) begin 
        menu_frm_addr <= menu_frm_addr + 32'b1;    
    end
end 
//--------------------------------------------------------
always @(posedge clk,negedge resetn) begin
    if(~resetn)begin 
        data_phase <= 2'b0;
    end 
    else if (~mm_video_en) begin
        data_phase <= 2'b10;
    end
    else if((cur_state == MENU_ADDR)&&(M_AXI_ARVALID)&&(M_AXI_ARREADY)) begin 
        data_phase <= data_phase + 2'b1;
    end
end 
//--------------------------------------------------------
reg[31:0] cur_cam0_addr;
reg[31:0] cur_cam1_addr;
reg[31:0] cur_menu_addr;
//--------------------------------------------------------
always @(posedge clk,negedge resetn) begin
    if(~resetn)begin 
        cur_cam0_addr <= 32'h0;    
        cur_cam1_addr <= 32'h0;    
        cur_menu_addr <= 32'h0;    
    end 
    else if(cur_state == IDLE) begin
        cur_cam0_addr <= video0_base_addr;    
        cur_cam1_addr <= video1_base_addr;    
        cur_menu_addr <= menu_base_addr;    
    end 
end 
//--------------------------------------------------------
reg[15:0] pix_integer;
//--------------------------------------------------------
always @(posedge clk,negedge resetn) begin
    if(~resetn)begin 
        M_AXI_ARADDR  <= 'b0;
        M_AXI_ARVALID <= 1'b0;
        pix_integer   <= 16'b0;
    end 
    else if(cur_state == MENU_ADDR ) begin 
        M_AXI_ARADDR  <= cur_menu_addr + {menu_frm_addr[29:0],2'b0};
        M_AXI_ARVALID <= (M_AXI_ARVALID&M_AXI_ARREADY)?1'b0:1'b1;
    end
    else if(cur_state == DISP_ADDR ) begin
        if( video0_only )begin
            M_AXI_ARADDR  <= cur_cam0_addr + {camx_frm_addr[29:0],2'b0};
        end
        else if( video1_only ) begin
            M_AXI_ARADDR  <= cur_cam1_addr + {camx_frm_addr[29:0],2'b0};
        end
        else if(camx_frm_addr[29:0] < disp_pix_cnt[31:3]) begin
            M_AXI_ARADDR  <= cur_cam0_addr + {camx_frm_addr[29:0],2'b0};
        end
        else begin
            M_AXI_ARADDR  <= cur_cam1_addr + {(camx_frm_addr[29:0] - disp_pix_cnt[31:3]),2'b0};
        end
        M_AXI_ARVALID <= (M_AXI_ARVALID&M_AXI_ARREADY)?1'b0:1'b1;    
    end
    else begin
        M_AXI_ARVALID <= 1'b0;  
    end
end   
//--------------------------------------------------------
wire video0_in_proc    = (~video0_only)&&(~video0_only)&&(camx_frm_addr[29:0] < disp_pix_cnt[31:3]);
wire video1_in_proc    = (~video0_only)&&(~video0_only)&&(camx_frm_addr[29:0] >= disp_pix_cnt[31:3]);
wire[31:0] cur_pix_cnt = mm_menu_en ? (menu_frm_addr + 2):{(camx_frm_addr + 2),2'b0};
assign frame_last      = (cur_pix_cnt >  disp_pix_cnt)?1'b1:1'b0;
assign M_AXI_RREADY    = ((cur_state == DISP_DATA)||(cur_state == MENU_DATA))?1'b1:1'b0;
//--------------------------------------------------------
wire[32:0 ] camx_din   = {frame_last,M_AXI_RDATA};
wire[32:0 ] menu_din   = {frame_last,M_AXI_RDATA};
wire        camx_wr_en = (cur_state == DISP_DATA)&&M_AXI_RVALID&&M_AXI_RREADY;
wire        menu_wr_en = (cur_state == MENU_DATA)&&M_AXI_RVALID&&M_AXI_RREADY;
wire        menu_full   ;
wire        underflow0  ;
wire        overflow0   ;
wire        underflow1  ;
wire        overflow1   ;
reg[32:0 ]  mixd_din    ;   
reg         mixd_wr_en  ;   
wire        camx_rd_en  ;
wire        menu_rd_en  ;
wire[32:0 ] menu_dout   ;
wire[32:0 ] camx_dout   ;
wire        disp_full   ;
wire        camx_empty  ;
wire        menu_empty  ;
reg[2:0 ]   mixd_stage  ;
//--------------------------------------------------------
wire   menu_pure_en = (~mm_video_en)&(~menu_empty);
wire   menu_vld     = mm_menu_en ? (~menu_empty) : 1'b1;
//--------------------------------------------------------
assign camx_rd_en   = (mixd_stage == 'd1);
assign menu_rd_en   = (menu_pure_en)|
                      ((mm_menu_en == 1)&&(mixd_stage >= 'd1)&&(mixd_stage < 'd5)&&(~menu_empty));
//--------------------------------------------------------
always @(posedge clk ,negedge resetn) begin
    if(~resetn)begin 
        mixd_stage <= 'b0;
    end
    else if((mixd_stage == 0))begin
        mixd_stage <= ((~disp_full)&(~camx_empty)&menu_vld)?(mixd_stage + 'b1):'b0; 
    end
    else begin
        mixd_stage <= mixd_stage + 'b1; 
    end
end
//--------------------------------------------------------
wire        mixed_proc   = (mixd_stage >= 2)&(mixd_stage < 6);
wire        mixed_byte3  = (mixd_stage == 5);
//--------------------------------------------------------
reg[32:0 ]  camx_dout_ff1;  
reg[32:0 ]  menu_dout_ff1;  
//--------------------------------------------------------
always @(posedge clk ,negedge resetn) begin
    if(~resetn)begin 
        camx_dout_ff1 <= 'b0;
        menu_dout_ff1 <= 'b0;
    end
    else begin
        if(menu_rd_en) menu_dout_ff1 <= menu_dout;
        if(camx_rd_en) camx_dout_ff1 <= camx_dout;
        if(mixed_proc) camx_dout_ff1[31:0] <= {camx_dout_ff1[23:0],8'b0};
    end
end
//--------------------------------------------------------
reg[1:0] mixed_flag;
always @(posedge clk)  mixed_flag <= {(&menu_dout[31:24]),(|menu_dout[31:24])} ; 
//--------------------------------------------------------
wire[23:0] mask_data  = mm_menu_en ? menu_dout_ff1[23:0] : 'b0;
wire[23:0] video_data = mm_menu_en ? {3{camx_dout_ff1[31:24]}} : 'b0;
//--------------------------------------------------------
wire[7:0] b_raw_data0 = mixed_flag[1] ? 8'b0 : (mixed_flag[0] ? {1'b0,video_data[7:1]  }:video_data[7:0]  );
wire[7:0] g_raw_data0 = mixed_flag[1] ? 8'b0 : (mixed_flag[0] ? {1'b0,video_data[15:9] }:video_data[15:8] );
wire[7:0] r_raw_data0 = mixed_flag[1] ? 8'b0 : (mixed_flag[0] ? {1'b0,video_data[23:17]}:video_data[23:16]);       
//--------------------------------------------------------
wire[7:0] b_raw_data1 = mixed_flag[1] ? mask_data[7:0]  : (mixed_flag[0] ? {1'b0,mask_data[7:1]  } : 8'b0 );
wire[7:0] g_raw_data1 = mixed_flag[1] ? mask_data[15:8] : (mixed_flag[0] ? {1'b0,mask_data[15:9] } : 8'b0 );
wire[7:0] r_raw_data1 = mixed_flag[1] ? mask_data[23:16]: (mixed_flag[0] ? {1'b0,mask_data[23:17]} : 8'b0 );        
//--------------------------------------------------------
wire[7:0] mixed_R     = r_raw_data1 + r_raw_data0 ;
wire[7:0] mixed_G     = g_raw_data1 + g_raw_data0 ;
wire[7:0] mixed_B     = b_raw_data1 + b_raw_data0 ;        
//--------------------------------------------------------
always @(posedge clk ,negedge resetn) begin
    if(~resetn)begin 
        mixd_din   <= 'b0;
        mixd_wr_en <= 'b0;
    end
    else if (~mm_video_en) begin
        mixd_din   <=  menu_dout;
        mixd_wr_en <=  menu_pure_en;
    end
    else if (mixed_proc) begin
        mixd_din[31:0] <= (mm_menu_en)?{8'h0,mixed_R,mixed_G,mixed_B}:{8'h0,{3{camx_dout_ff1[31:24]}}};
        mixd_din[32]   <=  mixed_byte3 ? camx_dout_ff1[32] : 1'b0;
        mixd_wr_en     <= 1'b1;
    end
    else begin
        mixd_din[32] <= 1'b0;
        mixd_wr_en   <= 1'b0;
    end
end
//--------------------------------------------------------
fifo_frm_buffer_33x fifo_inst1 (   
  .rst           ( ~resetn            ) ,
  .wr_clk        ( clk                ) , 

  .din           ( camx_din           ) ,
  .wr_en         ( camx_wr_en         ) ,
  .prog_full     ( prog_full          ) ,
  .prog_empty    (                    ) ,

  .rd_clk        ( clk                ) ,
  .rd_en         ( camx_rd_en         ) ,
  .dout          ( camx_dout          ) ,
  .empty         ( camx_empty         ) 
); 
//--------------------------------------------------------
fifo_frm_buffer_33x fifo_inst2 (   
  .rst           ( ~resetn            ) ,
  
  .wr_clk        ( clk                ) ,
  .din           ( menu_din           ) ,
  .wr_en         ( menu_wr_en         ) ,
  .prog_full     ( menu_full          ) ,
  .prog_empty    (                    ) ,

  .rd_clk        ( clk                ) ,
  .rd_en         ( menu_rd_en         ) ,
  .dout          ( menu_dout          ) ,
  .empty         ( menu_empty         ) 
); 
//--------------------------------------------------------
fifo_frm_buffer_33x fifo_inst0 (   
  .rst           ( ~resetn            ) ,
  
  .wr_clk        ( clk                ) ,    
  .din           ( mixd_din           ) ,
  .wr_en         ( mixd_wr_en         ) ,
  .prog_full     ( disp_full          ) ,
  .prog_empty    (                    ) ,

  .rd_clk        ( disp_rd_clk        ) ,
  .rd_en         ( disp_rd_en         ) ,
  .dout          ( disp_dout          ) ,
  .empty         ( disp_empty         ) 
); 
//--------------------------------------------------------
always @(posedge clk,negedge resetn) begin
    if(~resetn)begin
        disp_flow_err <= 2'b0;
        menu_flow_err <= 2'b0;
    end
    else begin
        disp_flow_err <= {overflow0,underflow0};
        menu_flow_err <= {overflow1,underflow1};
    end
end
//--------------------------------------------------------
always @(posedge clk,negedge resetn) begin
    if(~resetn)begin
        videos_disp_cnt <= 32'b0;
    end
    else if(mixd_wr_en&mixd_din[32]) begin
        videos_disp_cnt <= videos_disp_cnt + 32'b1;
    end
end
//--------------------------------------------------------
function integer clogb2 (input integer bit_depth);              
    begin                                                           
    for(clogb2=0; bit_depth>0; clogb2=clogb2+1)                   
        bit_depth = bit_depth >> 1;                                 
    end                                                           
endfunction   
//--------------------------------------------------------
assign M_AXI_AWID      = 'b0;
assign M_AXI_AWLEN	   = C_M_AXI_BURST_LEN - 1;            //burst length: 16  
assign M_AXI_AWSIZE	   = clogb2((C_M_AXI_DATA_WIDTH/8)-1); //Size should be C_M_AXI_DATA_WIDTH, in 2^SIZE bytes
assign M_AXI_AWBURST   = 2'b01     ;                       //00:fix,01:incr,10:wrap,11:rsv 
assign M_AXI_AWLOCK    = 2'b00     ;   //
assign M_AXI_AWCACHE   = 4'b0011   ;   /////  
assign M_AXI_AWQOS     = 4'b0000   ;   //
assign M_AXI_AWUSER	   = 'b1       ;

assign M_AXI_WSTRB	   = {(C_M_AXI_DATA_WIDTH/8){1'b1}};
assign M_AXI_WUSER	   = 'b0;

assign M_AXI_ARLEN	   = C_M_AXI_BURST_LEN - 1;
assign M_AXI_ARSIZE    = clogb2((C_M_AXI_DATA_WIDTH/8)-1);
assign M_AXI_ARBURST   = 2'b01;
assign M_AXI_ARCACHE   = 4'b0011;

assign M_AXI_BREADY    = 'b1;

endmodule
